/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#ifndef CORE_DEFAULTACSCLIENT_H_
#define CORE_DEFAULTACSCLIENT_H_

#include <memory>
#include <assert.h>

#include "IAcsClient.h"
#include "reader/ReaderFactory.h"
#include "profile/DefaultProfile.h"

#include <iostream>

namespace aliyun {

class DefaultAcsClient : public IAcsClient {
 public:
  typedef http::HttpRequest HttpRequest;
  typedef reader::Reader Reader;
  typedef reader::ReaderFactory ReaderFactory;
  typedef regions::ProductDomain ProductDomain;
  typedef transform::UnmarshallerContext UnmarshallerContext;

  DefaultAcsClient()
      : maxRetries_(3),
        autoRetry_(true) {
    clientProfile_ = profile::DefaultProfile::getProfile();
    reader_ = NULL;
  }

  DefaultAcsClient(IClientProfile* profile)
      : maxRetries_(3),
        autoRetry_(true) {
    clientProfile_ = profile;
    reader_ = NULL;
  }

  ~DefaultAcsClient() {
    if (reader_ != NULL) {
      delete reader_;
    }
  }

  virtual HttpResponse doAction(AcsRequestBase& request) throw (ClientException,
      ServerException) {
    return doAction(request, autoRetry_, maxRetries_, clientProfile_);
  }

  virtual HttpResponse doAction(AcsRequestBase& request, bool autoRetry,
                                int maxRetries) throw (ClientException,
                                    ServerException) {
    return doAction(request, autoRetry, maxRetries, clientProfile_);
  }

  virtual HttpResponse doAction(AcsRequestBase& request,
                                IClientProfile* profile) throw (ClientException,
                                    ServerException) {
    return doAction(request, autoRetry_, maxRetries_, profile);
  }

  virtual HttpResponse doAction(AcsRequestBase& request, string regionId,
                                Credential credential) throw (ClientException,
                                    ServerException) {
    ISigner* signer = NULL;
    FormatType format;
    std::vector<Endpoint> endpoints;
    if (NULL != clientProfile_) {
      signer = clientProfile_->getSigner();
      format = clientProfile_->getFormat();
      endpoints = clientProfile_->getEndpoints();
    }
    return doAction(request, autoRetry_, maxRetries_, regionId, credential,
                    signer, format, endpoints);
  }

  virtual HttpResponse doAction(AcsRequestBase& request, bool autoRetry,
                                int maxRetries, IClientProfile* profile)
                                    throw (ClientException, ServerException) {
    assert(profile != NULL);

    string region = profile->getRegionId();
    ISigner* signer = profile->getSigner();
    Credential credential = profile->getCredential();
    FormatType format = profile->getFormat();
    std::vector<Endpoint> endpoints = profile->getEndpoints();

    return doAction(request, autoRetry, maxRetries, region, credential, signer,
                    format, endpoints);
  }

  virtual HttpResponse doAction(AcsRequestBase& request, bool autoRetry,
                                int maxRetries, string regionId,
                                Credential credential, ISigner* signer,
                                FormatType format,
                                const std::vector<Endpoint>& endpionts)
                                    throw (ClientException, ServerException) {
    try {
      FormatType reqFormat = request.getAcceptFormat();
      if (reqFormat != FormatType::INVALID) {
        format = reqFormat;
      }
      if (request.getRegionId().size() == 0) {
        request.setRegionId(regionId);
      }

      const ProductDomain* domain = Endpoint::findProductDomain(
          endpionts, regionId, request.getProduct());
      if (domain == NULL) {
        throw ClientException("SDK.InvalidRegionId",
                              "Can not find endpoint to access.");
      }

      int retryTimes = 1;
      HttpRequest& req = request.signRequest(signer, credential, format,
                                             *domain);
      HttpResponse response = HttpResponse::getResponse(req);
      while (500 <= response.getStatus() && autoRetry && retryTimes < maxRetries) {
        req = request.signRequest(signer, credential, format, *domain);
        response = HttpResponse::getResponse(req);
        retryTimes++;
      }

      return response;
    } catch (...) {
      // TODO(xu): more detailed exception handling.
      throw;
    }
  }

  template<class T>
  T getAcsResponse(AcsRequest<T>& request) throw (ClientException,
      ServerException) {
    HttpResponse httpResponse = this->doAction(request);
    return parseAcsResponse<T>(httpResponse);
  }

  template<class T>
  T getAcsResponse(AcsRequest<T>& request, bool autoRetry, int maxRetries)
                       throw (ClientException, ServerException) {
    HttpResponse httpResponse = this->doAction(request, autoRetry, maxRetries);
    return parseAcsResponse<T>(httpResponse);
  }

  template<class T>
  T getAcsResponse(AcsRequest<T>& request, IClientProfile* profile)
                       throw (ClientException, ServerException) {
    HttpResponse httpResponse = this->doAction(request, profile);
    return parseAcsResponse<T>(httpResponse);
  }

  template<class T>
  T getAcsResponse(AcsRequest<T>& request, string regionId,
                   Credential credential) throw (ClientException,
                       ServerException) {
    HttpResponse httpResponse = this->doAction(request, regionId, credential);
    return parseAcsResponse<T>(httpResponse);
  }

 private:

  // depends on T::className(), T::unmarshall()
  template<class T>
  T parseAcsResponse(HttpResponse httpResponse) throw (ClientException,
      ServerException) {
    std::cout << "prepare to " << __FUNCTION__ << ": " << T::className() << std::endl;
    return T::unmarshall(readContext(httpResponse, T::className()));
  }

  static string getResponseContent(HttpResponse httpResponse) {
    string content;
    if (httpResponse.getEncoding().length() == 0) {
      content = httpResponse.getContent();
    } else {
      // TODO(xu): handle encoding.
      content = httpResponse.getContent();
    }
    return content;
  }

  UnmarshallerContext readContext(HttpResponse httpResponse, string endpoint)
                                      throw (ClientException, ServerException) {
    FormatType format = httpResponse.getContentType();
    if (httpResponse.isSuccess()) {
      UnmarshallerContext context;
      context.setHttpResponse(httpResponse);
      string content = getResponseContent(httpResponse);
      context.setResponseMap(getReader(format)->read(content, endpoint));
      return context;
    } else {
      AcsError err = readError(httpResponse, format);
      if (500 <= httpResponse.getStatus()) {
        throw ServerException(err.getErrorCode(), err.getErrorMessage(),
                              err.getRequestId());
      } else {
        throw ClientException(err.getErrorCode(), err.getErrorMessage(),
                              err.getRequestId());
      }
    }
  }

  AcsError readError(HttpResponse httpResponse, FormatType format) {
    UnmarshallerContext context;
    string content = getResponseContent(httpResponse);
    context.setResponseMap(getReader(format)->read(content, "Error"));

    return AcsError::unmarshall(context);
  }

  // update and get reader
  Reader* getReader(FormatType format) {
    if (reader_ != NULL) {
      delete reader_;
    }
    reader_ = ReaderFactory::createInstance(format);
    return reader_;
  }

 private:
  int maxRetries_;
  bool autoRetry_;
  IClientProfile* clientProfile_;
  Reader* reader_;  // need free
};

}  // namespace aliyun

#endif  // CORE_DEFAULTACSCLIENT_H_
